<template>
  <div class="full" id="3dMapBox">
    <div class="full" id="threeJs"></div>
  </div>
</template>

<script setup lang="js">
import * as THREE from "three";
import * as d3geo from "d3-geo";
import * as TWEEN from '@tweenjs/tween.js'
import { ref, onMounted } from 'vue';
import wsqJson from '../../assets/json/wsq.json'
import wsqBroundaryJson from '../../assets/json/wsq-broundary.json'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'

const emit = defineEmits(['chick3DMap'])

const center = [108.803886,38.597952]
// 墨卡托投影转换
const projection = d3geo.geoMercator().center(center).scale(500).translate([0, 0])
const mapEdgeLightObj = {
  mapEdgePoints: [],
  lightOpacityGeometry: null,  // 单独把geometry提出来，动画用

  // 边缘流光参数
  lightSpeed: 3,
  lightCurrentPos: 0,
  lightOpacitys: null,
}

// 场景
const scene = new THREE.Scene()
scene.position.set(0, 0, 0)

// 相机
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000)
camera.position.set(0, 20, 20)

// 环境光
const ambient = new THREE.AmbientLight(0x06dcf8, 5.0)
scene.add(ambient)

// 平行光1
// let directionalLight1 = new THREE.DirectionalLight(0x7af4ff, 1)
// directionalLight1.position.set(117.7179630504896, 44.61734576753193, 30)
// scene.add(directionalLight1)

// // 平行光2
// let directionalLight2 = new THREE.DirectionalLight(0x7af4ff, 1)
// directionalLight2.position.set(117.7179630504896, 44.61734576753193, 30)
// scene.add(directionalLight2)

// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.setClearColor(0x0F1427)

// document.body.appendChild(renderer.domElement)

// css 2d 渲染器
let labelRenderer = new CSS2DRenderer()
labelRenderer.setSize(window.innerWidth, window.innerHeight)
labelRenderer.domElement.style.position = 'absolute'
labelRenderer.domElement.style.top = '0px'
labelRenderer.domElement.style.pointerEvents = 'none'
// document.body.appendChild(labelRenderer.domElement)

// 创建后处理对象EffectComposer
const composer = new EffectComposer(renderer);

let clock = new THREE.Clock()

const texture = new THREE.TextureLoader()

var hoverIntersects = []
var clickIntersects = []

const img1 = ref()
const img2 = ref()
const uniforms = { iTime: { value: 0.1 } }

const bufferGeometry = new THREE.BufferGeometry()

const lines = []
let positions = null
let opacitys = null
let indexBol = true
let points = []
let point_lines = []

onMounted(() => {
  document.getElementById("threeJs").appendChild(renderer.domElement)
  document.getElementById("threeJs").appendChild(labelRenderer.domElement)
  initModel()

  loop()

  // 监听 浏览器 窗口变化， 适配 渲染器 大小
  window.onresize = () => {
    camera.aspect = window.innerWidth / window.innerHeight;
    // 更新相机投影矩阵
    camera.updateProjectionMatrix();
    // 重新设置渲染器渲染范围
    renderer.setSize(window.innerWidth, window.innerHeight);
    labelRenderer.setSize(window.innerWidth, window.innerHeight)
  }
})

const initModel = () => {
  // initAxes()
  init3DMap()

  initCirclePoint(scene, 30)
  img1.value = initRotating1(scene, 40)
  img2.value = initRotating2(scene, 20)
  initSceneBg(scene, 50)

  // 流光效果
  // flowLight()

  // 波浪效果
  initFloor()

  // 区域线发光
  luminesence(point_lines)
}

// 处理 GeoJson 地图
const init3DMap = () => {
  // 循环显示 地图区域块
  wsqJson.features.forEach(item => {
    if (item.geometry.type == 'MultiPolygon') {
      item.geometry.coordinates.forEach(val => {
        val.forEach(v => renderMapChildren(v, '#b4dff3', item.properties))
      })
    }

    if (item.geometry.type == 'Polygon') {
      item.geometry.coordinates.forEach(val => renderMapChildren(val, '#b4dff3', item.properties))
    }
  })
}

// 生成 3D 地图
const renderMapChildren = (mapData, color, properties) => {
  const map_block = new THREE.Object3D()
  const point = []
  const linePoints1 = []
  const linePoints2 = []

  mapData.forEach(val => {
    const [x, y] = projection(val)
    point.push(new THREE.Vector3(x, y, 0))
    linePoints1.push(new THREE.Vector3(x, y, -0.05))
    linePoints2.push(new THREE.Vector3(x, y, 1.5))
  });

  // 显示 label
  initLabel(properties)

  // 涟漪效果
  // initLightHao(properties)

  const shape = new THREE.Shape(point)

  const extrudeSettings = {
    depth: 1.5,
    bevelEnabled: false,
    bevelThickness: 1,
    bevelSize: 1,
    bevelOffset: 0,
    bevelSegments: 1
  }

  // 地图区域
  const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)

  // 贴图
  const textureMap = texture.load(`images/gz-map.jpg`)
  textureMap.wrapS = THREE.MirroredRepeatWrapping;
  textureMap.wrapT = THREE.MirroredRepeatWrapping;
  textureMap.repeat.set(0.1, 0.1);
  texture.magFilter = THREE.NearestFilter
  textureMap.rotation = THREE.MathUtils.degToRad(45)

  const material = new THREE.MeshPhongMaterial({
    map: textureMap,
    normalMap: textureMap,
    color,
    opacity: 1,
    combine: THREE.MultiplyOperation,
    transparent: true,
    depthTest: true,
    side: THREE.FrontSide
  })

  // 突出材料
  const sideMaterial = new THREE.MeshLambertMaterial({
    transparent: true,
    opacity: 0.5,
    side: THREE.FrontSide,
    bumpScale: 0.1,
    flatShading: true
  })
  const mesh = new THREE.Mesh(geometry, [material, sideMaterial])
  mesh.position.set(0, 0, 0)
  mesh.name = properties.name
  mesh["isMapBlock"] = true
  map_block.add(mesh)

  // 地图区域边界线 - 顶部
  const lineGeometry1 = new THREE.BufferGeometry().setFromPoints(linePoints1)
  const lineMaterial1 = new THREE.LineBasicMaterial({ color: 0xffffff });
  const line1 = new THREE.Line(lineGeometry1, lineMaterial1);
  point_lines.push(line1)
  map_block.add(line1);

  // 地图区域边界线 - 底部
  const lineGeometry2 = new THREE.BufferGeometry().setFromPoints(linePoints2)
  const lineMaterial2 = new THREE.LineBasicMaterial({ color: 0xffffff });
  const line2 = new THREE.Line(lineGeometry2, lineMaterial2);
  point_lines.push(line2)
  map_block.add(line2);

  map_block.properties = properties
  map_block.rotateX(Math.PI / 2)
  map_block.position.set(0, 3, 0)
  scene.add(map_block)
}

// 坐标轴
const initAxes = () => {
  let axes = new THREE.AxesHelper(1000)
  scene.add(axes)
}

// 光照
const initLight = () => {
  const light = new THREE.PointLight(0xffffff, 1);
  light.position.set(-20, -10, 10);
  scene.add(light);

  // 光源辅助线
  const lightHelper = new THREE.PointLightHelper(light);
  scene.add(lightHelper);
}

// 涟漪
const initLightHao = (properties) => {

  // 标记点：几何体，材质，
  let geometry = new THREE.PlaneGeometry(1, 1)
  let material = new THREE.MeshBasicMaterial({
    map: texture.load(`images/guang-quan.png`),
    color: 0xfffffff,
    side: THREE.DoubleSide,
    opacity: 0,
    transparent: true,
    depthWrite: false, //禁止写入深度缓冲区数据
  })
  let mesh = new THREE.Mesh(geometry, material)
  mesh.renderOrder = 98
  mesh.name = "createLightHalo"
  // 缩放
  let scale = 1
  mesh.scale.set(scale, scale, scale)
  // 动画延迟时间
  let delay = random(0, 2000)
  // 动画：透明度缩放动画
  mesh.tween1 = new TWEEN.Tween({ scale: scale, opacity: 0 })
    .to({ scale: scale * 1.5, opacity: 1 }, 1000)
    .delay(delay)
    .onUpdate((params) => {
      let { scale, opacity } = params
      mesh.scale.set(scale, scale, scale)
      mesh.material.opacity = opacity
    })
  mesh.tween2 = new TWEEN.Tween({ scale: scale * 1.5, opacity: 1 })
    .to({ scale: scale * 2, opacity: 0 }, 1000)
    .onUpdate((params) => {
      let { scale, opacity } = params
      mesh.scale.set(scale, scale, scale)
      mesh.material.opacity = opacity
    })
  mesh.tween1.chain(mesh.tween2)
  mesh.tween2.chain(mesh.tween1)
  mesh.tween1.start()

  let [x, y] = projection(properties.center)
  console.log('涟漪 : ', x, y);
  mesh.position.set(x, 4, -y)
  mesh.rotateX(Math.PI / 2)
  scene.add(mesh)
}

const random = function (min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

// 鼠标 经过 地图 区域 
const onPointerMove = (event) => {
  const raycaster = new THREE.Raycaster();
  const pointer = new THREE.Vector2();

  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;

  // 通过摄像机和鼠标位置更新射线
  raycaster.setFromCamera(pointer, camera);
  if (hoverIntersects.length) {
    for (let i = 0; i < hoverIntersects.length; i++) {
      if (hoverIntersects[i].object.isMapBlock) {
        hoverIntersects[i].object.material[0].color.set('#b4dff3');
        // hoverIntersects[i].object.position.z += 1
      }
    }
  }

  // 计算物体和射线的焦点
  const intersects = raycaster.intersectObjects(scene.children);
  hoverIntersects = []

  // intersects 中 有 两个 name

  let active_map_block = {}


  for (let i = 0; i < intersects.length; i++) {
    if (intersects[i].object.type == 'Mesh' && intersects[i].object.isMapBlock && intersects[i].object.name) {
      active_map_block = intersects[i]

      hoverIntersects.push(active_map_block)
      // console.log('hoverIntersects : ', hoverIntersects);
      intersects[i].object.material[0].color.set('#01A7D3');
      // intersects[i].object.position.z -= 1
    }
  }
}

// 点击 地图 区域
const clickMapBlock = (event) => {
  // 将鼠标坐标 转换为 Threejs 中的坐标
  const mouse = new THREE.Vector2()
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
  mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;

  // 发射射线
  const rayCaster = new THREE.Raycaster()
  rayCaster.setFromCamera(mouse, camera)

  // 检测相交对象
  clickIntersects = rayCaster.intersectObjects(scene.children)

  // 如果相交的是地图部分
  if (clickIntersects.length > 0 && clickIntersects[0].object.isMapBlock) {
    let map_block_name = clickIntersects[0].object.name
    // console.log('当前点击区域 : ', map_block_name);

    emit('chick3DMap', map_block_name)
  }
}

// 背景图 模糊图
const initSceneBg = (scene, width) => {
  const img = texture.load(`images/scene-bg2.png`)
  let plane = new THREE.PlaneGeometry(width, width)
  let material = new THREE.MeshPhongMaterial({
    color: 0x061920,
    color: 0xffffff,
    map: img,
    transparent: true,
    opacity: 1,
    depthTest: true,
  })

  let mesh = new THREE.Mesh(plane, material)
  mesh.position.set(0, -2, 0)
  mesh.rotateX(-Math.PI / 2)
  scene.add(mesh)
}

// 背景图 点
const initCirclePoint = (scene, width) => {
  let img = texture.load(`images/circle-point.png`)
  let plane = new THREE.PlaneGeometry(width, width)
  let material = new THREE.MeshBasicMaterial({
    // color: 0x00ffff,
    map: img,
    transparent: true,
    opacity: 1,
    // depthTest: false,
  })
  let mesh = new THREE.Mesh(plane, material)
  mesh.position.set(0, -1.5, 0)
  mesh.rotateX(-Math.PI / 2)
  scene.add(mesh)
}

// 背景图 光圈1
const initRotating1 = (scene, width) => {
  let img = texture.load(`images/rotatingAperture.png`)
  let plane = new THREE.PlaneGeometry(width, width)
  let material = new THREE.MeshBasicMaterial({
    map: img,
    transparent: true,
    opacity: 1,
    depthTest: true,
  })
  let mesh = new THREE.Mesh(plane, material)
  mesh.position.set(0, -1, 0)
  scene.add(mesh)
  mesh.rotateX(-Math.PI / 2)
  return mesh
}

// 背景图 光圈2
const initRotating2 = (scene, width) => {
  let img = texture.load(`images/rotating-point2.png`)
  let plane = new THREE.PlaneGeometry(width, width)
  let material = new THREE.MeshBasicMaterial({
    map: img,
    transparent: true,
    opacity: 1,
    depthTest: true,
  })
  let mesh = new THREE.Mesh(plane, material)
  mesh.position.set(0, -0.5, 0)
  mesh.rotateX(-Math.PI / 2)
  scene.add(mesh)
  return mesh
}

// 显示 地图区域块 label
const initLabel = (properties) => {
  let center = properties.center
  let [x, y] = projection(center)
  let tag = document.createElement('div')
  tag.innerHTML = properties.name
  tag.style.color = '#ffffff'
  tag.style.position = 'absolute'
  tag.style.fontSize = '10'

  let label = new CSS2DObject(tag)
  label.element.style.visibility = 'visible'
  label.position.set(x, 3.5, y)
  scene.add(label)
}

// 分散效果
const initFloor = () => {
  let vextureShader = `
      varying vec2 vUv;
      varying vec3 fNormal;
      varying vec3 vPosition;
      void main()
      {
      vUv = uv;
      fNormal=normal;
      vPosition=position;
      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
      }
    `

  let fragmentShader = `
      varying vec2 vUv;
      uniform float iTime;
      uniform sampler2D colorTexture;
      vec3 hsb2rgb(const vec3 c) {
      vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0, 4.0, 2.0), 6.0)-3.0)-1.0, 0.0, 1.0);
        rgb = rgb*rgb*(2.0-2.0*rgb);
        return c.z * mix( vec3(1.0), rgb, c.y);
      }
      void main() {
        vec2 uv = vUv;
        vec2 p = 2.0*uv.xy - vec2(1., 1.) ; 
        float r = length(p) * 1.; //圈数量
        vec3 color = hsb2rgb(vec3(0.4, 1., 1.)); //0.24, 0.7, 0.5
        vec4 textureValue = texture2D( colorTexture, vUv );
        float a = pow(r, 2.0); //圈数量
        float b = sin(r * 0.8 - 1.6);
        float c = sin(r - .10);
        float s = sin(a - iTime * 3.0 + b) * c;
        color *= abs(1.0 / (s * 30.)) - 0.3; //初始圈大小
        gl_FragColor = vec4(color, 1.);
      }
    `

  //扩散波效果
  let circle_geometry = new THREE.CircleGeometry(40, 20);
  var material = new THREE.ShaderMaterial({
    uniforms: uniforms,
    vertexShader: vextureShader,
    fragmentShader: fragmentShader,
    side: THREE.DoubleSide,
    blending: THREE.AdditiveBlending,
    transparent: true,
    opacity: 0.9
  })
  let plane2 = new THREE.Mesh(circle_geometry, material);
  plane2.rotateX(Math.PI / 2)
  plane2.position.set(0, 0, 0)
  scene.add(plane2);
}

// 流光效果
const flowLight = () => {
  const feature = wsqBroundaryJson.features[0]
  const province = new THREE.Object3D()
  province.properties = feature.properties.name
  // 点数据
  const coordinates = feature.geometry.coordinates

  coordinates.forEach(coordinate => {
    // coordinate 多边形数据
    coordinate.forEach(rows => {

      // 绘制 外边
      const line = lineDraw(rows, 0x00A295)
      line.rotateX(Math.PI / 2)
      // province.add(line)
    })
  })

  positions = new Float32Array(lines.flat(1))
  // 设置顶点
  bufferGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
  // 设置 粒子透明度为 0
  opacitys = new Float32Array(positions.length).map(() => 0)
  bufferGeometry.setAttribute('aOpacity', new THREE.BufferAttribute(opacitys, 1))

  scene.add(province)

  function lineDraw(polygon, color) {
    const lineGeometry = new THREE.BufferGeometry()
    const pointsArray = new Array()

    polygon.forEach((row) => {
      const [x, y] = projection(row)
      // 创建三维点
      pointsArray.push(new THREE.Vector3(x, 3, y))

      if (indexBol) {
        lines.push([x, 3, y])
      }
    })

    indexBol = false
    // 放入多个点
    lineGeometry.setFromPoints(pointsArray)

    const lineMaterial = new THREE.LineBasicMaterial({
      color: color
    })
    return new THREE.Line(lineGeometry, lineMaterial)
  }

  // 控制 颜色和粒子大小
  const params = {
    pointSize: 5.0,
    pointColor: '#0632f8'
  }

  const vertexShader = `
      attribute float aOpacity;
      uniform float uSize;
      varying float vOpacity;

      void main(){
        gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.0);
        gl_PointSize = uSize;

        vOpacity=aOpacity;
      }
      `

  const fragmentShader = `
      varying float vOpacity;
      uniform vec3 uColor;

      float invert(float n){
        return 1.-n;
      }

      void main(){
        if(vOpacity <=0.2){
          discard;
        }
        vec2 uv=vec2(gl_PointCoord.x,invert(gl_PointCoord.y));
        vec2 cUv=2.*uv-1.;
        vec4 color=vec4(1./length(cUv));
        color*=vOpacity;
        color.rgb*=uColor;
        gl_FragColor=color;
      }
      `
  const material = new THREE.ShaderMaterial({
    vertexShader: vertexShader,
    fragmentShader: fragmentShader,
    transparent: false, // 设置透明
    uniforms: {
      uSize: {
        value: params.pointSize
      },
      uColor: {
        value: new THREE.Color('#ffffff')
      }
    }
  })
  points = new THREE.Points(bufferGeometry, material)
  scene.add(points)
}

// 光柱
const lightPillar = (center) => {
  let [x, y] = projection(center)
  // 实现光柱
  let lightPillarTexture = new THREE.TextureLoader().load(
    "images/light-pillar.png"
  );
  let lightPillarGeometry = new THREE.PlaneGeometry(3, 5);
  let lightPillarMaterial = new THREE.MeshBasicMaterial({
    color: 0xffffff,
    map: lightPillarTexture,
    alphaMap: lightPillarTexture,
    transparent: true,
    blending: THREE.AdditiveBlending,
    side: THREE.DoubleSide,
    depthWrite: false,
  });
  let lightPillar = new THREE.Mesh(lightPillarGeometry, lightPillarMaterial);
  lightPillar.rotateX(Math.PI / 2);
  lightPillar.position.set(x, y, 8)
  scene.add(lightPillar)
}

// 地图区域边缘线 发光效果
const luminesence = (point_lines) => {
  //创建通道
  let renderScene = new RenderPass(scene, camera);
  composer.addPass(renderScene);

  let outlinePass = new OutlinePass(
    new THREE.Vector2(window.innerWidth, window.innerHeight),
    scene,
    camera,
    point_lines
  );
  outlinePass.renderToScreen = true;
  outlinePass.edgeGlow = 20; // 光晕效果
  outlinePass.usePatternTexture = true;
  outlinePass.edgeThickness = 10; // 边框宽度
  outlinePass.edgeStrength = 20; // 光晕效果
  outlinePass.pulsePeriod = 10; // 光晕闪烁的速度
  outlinePass.visibleEdgeColor.set("#f806e8");
  outlinePass.hiddenEdgeColor.set("#f806e8");
  composer.addPass(outlinePass);
}

let currentPos = 0
let pointSpeed = 2 // 速度

const loop = () => {
  TWEEN.update()
  // 辉光效果
  const timer = clock.getDelta();
  uniforms.iTime.value += Math.sin(timer) / 3;
  if (uniforms.iTime.value > 1.0) {
    uniforms.iTime.value = 0.0;
  }

  // 流光效果
  if (points && bufferGeometry.attributes.position) {
    currentPos += pointSpeed
    for (let i = 0; i < pointSpeed; i++) {
      opacitys[(currentPos - i) % lines.length] = 0
    }

    for (let i = 0; i < 200; i++) {
      opacitys[(currentPos + i) % lines.length] = i / 50 > 2 ? 1 : i / 50
    }
    bufferGeometry.attributes.aOpacity.needsUpdate = true
  }

  if (controls.visibel && controls) {
    controls.update()
  }

  requestAnimationFrame(loop);

  // 光圈 旋转
  if (img1.value) {
    img1.value.rotation.z += 0.002;
  }
  if (img2.value) {
    img2.value.rotation.z -= 0.002;
  }

  // 旋转 3D 地图
  // scene.rotation.y += 0.001;

  // composer.render();
  renderer.render(scene, camera)
  // if (composer) composer.render();
  labelRenderer.render(scene, camera)
}

// 相机控件： 鼠标控制 旋转 缩放
const controls = new OrbitControls(camera, renderer.domElement)
controls.visibel = true
// 在平面上旋转 （底部不可见）
controls.maxPolarAngle = Math.PI / 2;
controls.autoRotate = false;
controls.enableDamping = true;

controls.addEventListener('change', () => {
  renderer.render(scene, camera)
})

window.addEventListener('pointermove', onPointerMove);
window.addEventListener('click', clickMapBlock, false);
</script>

<style>
#3dMapBox {
  width: 100%;
  height: 100%;
  position: absolute;
  overflow: hidden;
}

#threeJs {
  width: 100%;
  height: 100%;
}

#label {
  position: absolute;
  top: 0;
  left: 0
}
</style>
